/*
 * The MIT License
 * 
 * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Stephen Connolly
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package hudson.slaves;

import hudson.ExtensionPoint;
import hudson.Util;
import hudson.DescriptorExtensionList;
import hudson.Extension;
import hudson.model.*;
import hudson.tasks.Mailer;
import hudson.util.DescriptorList;
import hudson.util.FormValidation;
import hudson.util.Secret;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import org.kohsuke.stapler.DataBoundConstructor;

import java.util.logging.Level;
import java.util.logging.Logger;
import javax.mail.*;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.servlet.ServletException;
import org.kohsuke.stapler.QueryParameter;

/**
 * Controls when to take {@link Computer} offline, bring it back online, or even to destroy it.
 *
 * @author Stephen Connolly
 * @author Kohsuke Kawaguchi
 */
public abstract class RetentionStrategy<T extends Computer> extends AbstractDescribableImpl<RetentionStrategy<?>> implements ExtensionPoint {

    /**
     * This method will be called periodically to allow this strategy to decide what to do with it's owning slave.
     *
     * @param c {@link Computer} for which this strategy is assigned. This computer may be online or offline.
     *          This object also exposes a bunch of properties that the callee can use to decide what action to take.
     * @return The number of minutes after which the strategy would like to be checked again. The strategy may be
     *         rechecked earlier or later that this!
     */
    public abstract long check(T c);

    /**
     * This method is called to determine whether manual launching of the slave is allowed at this point in time.
     * @param c {@link Computer} for which this strategy is assigned. This computer may be online or offline.
     *          This object also exposes a bunch of properties that the callee can use to decide if manual launching is
     * allowed at this time.
     * @return {@code true} if manual launching of the slave is allowed at this point in time.
     */
    public boolean isManualLaunchAllowed(T c) {
        return true;
    }

    /**
     * Called when a new {@link Computer} object is introduced (such as when Hudson started, or when
     * a new slave is added.)
     *
     * <p>
     * The default implementation of this method delegates to {@link #check(Computer)},
     * but this allows {@link RetentionStrategy} to distinguish the first time invocation from the rest.
     *
     * @since 1.275
     */
    public void start(T c) {
        check(c);
    }

    /**
     * Returns all the registered {@link RetentionStrategy} descriptors.
     */
    public static DescriptorExtensionList<RetentionStrategy<?>,Descriptor<RetentionStrategy<?>>> all() {
        return (DescriptorExtensionList)Hudson.getInstance().getDescriptorList(RetentionStrategy.class);
    }

    /**
     * All registered {@link RetentionStrategy} implementations.
     * @deprecated as of 1.286
     *      Use {@link #all()} for read access, and {@link Extension} for registration.
     */
    public static final DescriptorList<RetentionStrategy<?>> LIST = new DescriptorList<RetentionStrategy<?>>((Class)RetentionStrategy.class);

    /**
     * Dummy instance that doesn't do any attempt to retention.
     */
    public static final RetentionStrategy<Computer> NOOP = new RetentionStrategy<Computer>() {
        public long check(Computer c) {
            return 60;
        }

        @Override
        public void start(Computer c) {
            c.connect(false);
        }

        @Override
        public Descriptor<RetentionStrategy<?>> getDescriptor() {
            return DESCRIPTOR;
        }

        private final DescriptorImpl DESCRIPTOR = new DescriptorImpl();

        class DescriptorImpl extends Descriptor<RetentionStrategy<?>> {
            public String getDisplayName() {
                return "";
            }
        }
    };

    /**
     * Convenient singleton instance, since this {@link RetentionStrategy} is stateless.
     */
    public static final Always INSTANCE = new Always();

    /**
     * {@link RetentionStrategy} that tries to keep the node online all the time.
     */
    public static class Always extends RetentionStrategy<SlaveComputer> {
        /**
         * Constructs a new Always.
         */
        @DataBoundConstructor
        public Always() {
        }
        
        
        
        
        /**
         *@describe
         *     when this slavecomputer is offline ,the send email to it's creator to fixed this offline problems
         * 
         */
        private boolean sendEmailToSlaveManagersWhenOffLine(String subject_str,String html_string,String managers_email) throws MessagingException {
                MimeMessage msg = new MimeMessage(Mailer.descriptor().createSession());
                msg.setSubject(subject_str);
                Multipart mainPart = new MimeMultipart("related");
                BodyPart html = new MimeBodyPart();    
                html.setContent(html_string, "text/html;charset=UTF-8");    
                mainPart.addBodyPart(html); 
                msg.setContent(mainPart); 
                msg.setFrom(new InternetAddress(Mailer.descriptor().getAdminAddress()));
                msg.setSentDate(new Date());
                
                String [] managers_emails=managers_email.split(";");
                /**
                 *@since 2012/3/8 
                 *  find a bug here ,Nullpointer exception ,should delete the "" string
                 */
                List<String> list = new ArrayList<String>();
                for(int i=0;i<managers_emails.length;i++){
                    if(!"".equals(managers_emails[i].trim())){
                        list.add(managers_emails[i]);
                    }
                }
                /**
                 *@describe 
                 *@since 2012/3/8
                 *  java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;
                 */
                //managers_emails=(String[])(list.toArray());
                managers_emails = Arrays.copyOf(list.toArray(), list.toArray().length, String[].class);
                
                InternetAddress[] toDestination=new InternetAddress[managers_emails.length];
                for(int i=0;i<managers_emails.length;i++){
                    if(managers_emails[i].length()>0 && managers_emails[i].matches("\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*")){
                        toDestination[i]=new InternetAddress(managers_emails[i]);  
                    }else if(managers_emails[i].length()>0 && managers_emails[i].indexOf("@")<=0){
                        toDestination[i]=new InternetAddress(managers_emails[i]+Mailer.descriptor().getDefaultSuffix()); 
                    }
                }
                /**
                 *all received email address 
                 */
                if(toDestination.length>0){
                    msg.setRecipients(Message.RecipientType.TO ,toDestination);
                    Transport.send(msg);
                    return true;
                }else{
                    return false;
                }
        }
        
        /**
         *@describe 
         *  check this computer is offline or not
         */
        public long check(SlaveComputer c) {
            if(c.isOnline() && c.getNode().isIsSendEmailAlready()){
                c.getNode().setIsSendEmailAlready(false);
            }
            if (c.isOffline() && !c.isConnecting() && c.isLaunchSupported()){
                c.tryReconnect();
            }
            if (c.isOffline() && !c.isConnecting() ){
                try {
                    String managers_email=c.getNode().getSlaveManagersEmail()+";"+c.getNode().getSlaveCreator(); 
                    String sub_str="Node "+c.getNode().getNodeName()+" lost connection to master";                  
                    /**
                     * String html_string="连接方式 \n1、在节点机器上使用浏览器启动\n"+Hudson.getInstance().getRootUrl()+c.getNode().getSearchUrl()+"/slave-agent.jnlp\n2、在节点机器上使用cmd命令行启动\njavaws "+Hudson.getInstance().getRootUrl()+c.getNode().getSearchUrl()+"/slave-agent.jnlp\n或\njava -jar slave.jar -jnlpUrl "+Hudson.getInstance().getRootUrl()+c.getNode().getSearchUrl()+"/slave-agent.jnlp\n3、在节点机器上重新启动hudson_slave服务\n节点详细信息\n1、IP："+c.getNode().getSlaveIP()+"\n2、安装目录："+c.getNode().getRemoteFS()+"\n3、标签："+c.getNode().getLabelString()+"\n4、Executor:"+c.getNode().getNumExecutors()+"\n-----持续集成平台(请勿回复)-----";
                     *@describe
                     *  in windows chinese encode default is "GBK" , but hudson use internal encoding "UTF-8" ,but change the email 
                     *  chinese word encoding method to utf-8 ,we find that some world could encoding normally ,but some others word
                     *  encoding unnormallly, we use english instead those unnormal chinese word
                     * 
                     */
                    String html_content="</br>&nbsp;<span style='background-color:#73bae8;width:200px;font-size:20px;font-weight:600px'>1、Start Slave方式</span></br><span style='font-size:13px;font-weight:300px'>&nbsp;&nbsp;1、在节点机器上使用浏览器启动</span></br><span style='font-size:13px;font-weight:300px'>&nbsp;&nbsp;&nbsp;"+Hudson.getInstance().getRootUrl()+c.getNode().getSearchUrl()+"/slave-agent.jnlp</span></br><span style='font-size:13px;font-weight:300px'>&nbsp;&nbsp;2、在节点机器上使用cmd命令Start Slave</span></br><span style='font-size:13px;font-weight:300px'>&nbsp;&nbsp;&nbsp;javaws "+Hudson.getInstance().getRootUrl()+c.getNode().getSearchUrl()+"/slave-agent.jnlp</span></br>&nbsp;&nbsp;&nbsp;or</br><span style='font-size:13px;font-weight:300px'>&nbsp;&nbsp;&nbsp;java -jar slave.jar -jnlpUrl "+Hudson.getInstance().getRootUrl()+c.getNode().getSearchUrl()+"/slave-agent.jnlp</span></br><span style='font-size:13px;font-weight:300px'>&nbsp;&nbsp;3、在节点服务器上重新启动hudson_slave服务</span><div /></br>&nbsp;<span style='background-color:#73bae8;width:200px;font-size:20px;font-weight:600px'>2、节点information</span></br><span style='font-size:13px;font-weight:300px'>&nbsp;&nbsp;1、IP信息:  "+c.getNode().getSlaveIP()+"</span></br><span style='font-size:13px;font-weight:300px'>&nbsp;&nbsp;2、Remote FS:  "+c.getNode().getRemoteFS()+"</span></br><span style='font-size:13px;font-weight:300px'>&nbsp;&nbsp;3、节点label: "+c.getNode().getLabelString()+"</span></br><span style='font-size:13px;font-weight:300px'>&nbsp;&nbsp;4、Executor:   "+c.getNode().getNumExecutors()+"</span></br><hr style='border-top:1px solid #73bae8;width:97%'/><span style='font-size:12px;font-weight:400px;color:gray'>&nbsp;**此邮件由&nbsp;<a href='"+Hudson.getInstance().getRootUrl()+"'>持续集成平台</a>&nbsp;自动生成,请勿回复**</span></br><span>&nbsp;&nbsp;&nbsp;</span>";
                    String html_string="<div style='border-style:solid;border-width:5pt;border-color:#b9dbf4;background-color:#FFFFFF;margin-left:5px;margin-right:5px;height:435px;'><div style='background-color:#b9dbf4;'><font style='font-size:24px;font-weight:800px'><img src='"+Hudson.getInstance().getRootUrl()+"/images/logo.png' />&nbsp;</font></div>"+html_content+"</div>";
                    try {
                        sub_str=new String(sub_str.getBytes("GBK"),"UTF-8");
                        html_string=new String(html_string.getBytes("GBK"),"UTF-8");
                    } catch (UnsupportedEncodingException ex) {
                        Logger.getLogger(RetentionStrategy.class.getName()).log(Level.SEVERE, null, ex);
                    }
                    if(!c.getNode().isIsSendEmailAlready()){
                      boolean isRight=sendEmailToSlaveManagersWhenOffLine(sub_str,html_string,managers_email);
                      if(isRight){
                          c.getNode().setIsSendEmailAlready(true);
                      }else{
                          c.getNode().setIsSendEmailAlready(false);
                      }
                    }
                } catch (MessagingException ex) {
                    Logger.getLogger(RetentionStrategy.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
            System.out.println("RetentionStrategy class : check method : line 142 : "+c.getNode().getSlaveManagersEmail());
            return 0;
        }

        @Extension(ordinal=100)
        public static class DescriptorImpl extends Descriptor<RetentionStrategy<?>> {
            public String getDisplayName() {
                return Messages.RetentionStrategy_Always_displayName();
            }
        }
    }

    /**
     * {@link hudson.slaves.RetentionStrategy} that tries to keep the node offline when not in use.
     */
    public static class Demand extends RetentionStrategy<SlaveComputer> {

        private static final Logger logger = Logger.getLogger(Demand.class.getName());

        /**
         * The delay (in minutes) for which the slave must be in demand before tring to launch it.
         */
        private final long inDemandDelay;

        /**
         * The delay (in minutes) for which the slave must be idle before taking it offline.
         */
        private final long idleDelay;

        @DataBoundConstructor
        public Demand(long inDemandDelay, long idleDelay) {
            this.inDemandDelay = Math.max(0, inDemandDelay);
            this.idleDelay = Math.max(1, idleDelay);
        }

        /**
         * Getter for property 'inDemandDelay'.
         *
         * @return Value for property 'inDemandDelay'.
         */
        public long getInDemandDelay() {
            return inDemandDelay;
        }

        /**
         * Getter for property 'idleDelay'.
         *
         * @return Value for property 'idleDelay'.
         */
        public long getIdleDelay() {
            return idleDelay;
        }

        public synchronized long check(SlaveComputer c) {
            if (c.isOffline()) {
                final long demandMilliseconds = System.currentTimeMillis() - c.getDemandStartMilliseconds();
                if (demandMilliseconds > inDemandDelay * 1000 * 60 /*MINS->MILLIS*/ && c.isLaunchSupported()) {
                    // we've been in demand for long enough
                    logger.log(Level.INFO, "Launching computer {0} as it has been in demand for {1}",
                            new Object[]{c.getName(), Util.getTimeSpanString(demandMilliseconds)});
                    c.connect(false);
                }
            } else if (c.isIdle()) {
                final long idleMilliseconds = System.currentTimeMillis() - c.getIdleStartMilliseconds();
                if (idleMilliseconds > idleDelay * 1000 * 60 /*MINS->MILLIS*/) {
                    // we've been idle for long enough
                    logger.log(Level.INFO, "Disconnecting computer {0} as it has been idle for {1}",
                            new Object[]{c.getName(), Util.getTimeSpanString(idleMilliseconds)});
                    c.disconnect(OfflineCause.create(Messages._RetentionStrategy_Demand_OfflineIdle()));
                }
            }
            return 1;
        }

        @Extension
        public static class DescriptorImpl extends Descriptor<RetentionStrategy<?>> {
            public String getDisplayName() {
                return Messages.RetentionStrategy_Demand_displayName();
            }
        }
    }
}
